Skip to main content

Mobile — Technical Reference

This document covers the technical details for building mobile Aspects that integrate with the Candescent Digital Banking platform. Mobile Aspects run inside a WebView managed by the native app, which introduces a fundamentally different execution model compared to web Aspects.

For the web-specific technical reference, see Web Technical Reference.

For a side-by-side comparison of how web and mobile Aspects differ, see the Web vs. Mobile table in the Aspects Overview.


HTML Wrapper Structure

Mobile Aspects are delivered as full HTML documents. The platform loads this HTML into a WebView. Unlike web Aspects (which are plain .js files), the mobile Aspect must include the HTML scaffolding.

<html>
<head></head>
<body>
<script type="text/javascript">
// Meta tag injection
var metaCharset = document.createElement('meta');
metaCharset.setAttribute('charset', 'utf-8');
document.head.appendChild(metaCharset);

var metaViewport = document.createElement('meta');
metaViewport.setAttribute('name', 'viewport');
metaViewport.setAttribute('content', 'width=device-width, initial-scale=1, shrink-to-fit=no');
document.head.appendChild(metaViewport);

// Your Aspect code goes here
</script>
</body>
</html>

Meta tags for charset and viewport should be injected programmatically at the start of the script for consistent rendering across devices.


Native Bridge Communication

The native app exposes bridge interfaces that allow the Aspect to communicate with the host app. There are two bridges, each with separate APIs for iOS and Android.

Bridge Detection Pattern

All bridge calls must handle both iOS and Android:

function sendToNativeBridge(bridgeName, payload) {
var jsonString = JSON.stringify(payload);
if (navigator.userAgent && navigator.userAgent.match(/(iPad|iPhone|iPod)/i)) {
window.webkit.messageHandlers[bridgeName].postMessage(jsonString);
} else {
JSBridge[bridgeName](jsonString);
}
}

Available Bridges

BridgePurposeiOS APIAndroid API
tokenApiDetailsRequest authentication — sends API call details to the native app for executionwindow.webkit.messageHandlers.tokenApiDetails.postMessage(json)JSBridge.tokenApiDetails(json)
sizeAndLocationReport layout requirements — tells the native app how to size and position the WebViewwindow.webkit.messageHandlers.sizeAndLocation.postMessage(json)JSBridge.sizeAndLocation(json)

Authentication via Native Bridge

Mobile Aspects cannot use fetch() with session cookies like web Aspects do. Instead, authentication is delegated to the native app through the tokenApiDetails bridge.

How It Works

  1. The Aspect sends API call details (URL, method, headers) to the native app via tokenApiDetails.
  2. The native app performs the HTTP request on behalf of the Aspect, using its own session credentials.
  3. When the response is ready, the native app sets window.dbk.tokenResponse and dispatches the apiToken:ready event.
  4. The Aspect listens for the event and uses the token to initialize the vendor SDK.

Sending Authentication Request

var apiCallDetails = {
method: 'GET',
headers: {
cookie: ''
},
url: 'https://<FI_DOMAIN>/feng-bff/live/v1/aspect/token?clientId=<YOUR_CLIENT_ID>'
};

var jsonString = JSON.stringify(apiCallDetails);
if (navigator.userAgent && navigator.userAgent.match(/(iPad|iPhone|iPod)/i)) {
window.webkit.messageHandlers.tokenApiDetails.postMessage(jsonString);
} else {
JSBridge.tokenApiDetails(jsonString);
}

Receiving the Token

async function getAuthToken() {
// Check if the token is already available
if (window.dbk && window.dbk.tokenResponse) {
return window.dbk.tokenResponse;
}
// Wait for the native app to deliver the token
return new Promise(function (resolve) {
var onReady = function () {
window.removeEventListener('apiToken:ready', onReady);
resolve(window.dbk.tokenResponse);
};
window.addEventListener('apiToken:ready', onReady);
});
}

Comparison with web authentication:

StepWebMobile
Send requestfetch(tokenUrl, { headers: { Cookie: document.cookie } })tokenApiDetails bridge → native app executes request
Receive responsefetch() Promise resolves with JSONwindow.dbk.tokenResponse + apiToken:ready event
Session credentialsSession cookies sent via document.cookieHandled by native app (cookies not available in WebView)

Script Loading

Mobile Aspects do not have access to dbk.loadScript(). Instead, load external scripts manually using document.createElement('script'):

function loadScript(url) {
return new Promise(function (resolve, reject) {
var script = document.createElement('script');
script.src = url;
script.type = 'text/javascript';
script.onload = resolve;
script.onerror = function () {
reject(new Error('Failed to load script: ' + url));
};
document.body.appendChild(script);
});
}

loadScript('https://cdn.your-vendor.com/sdk/v3/widget.js')
.then(function () {
// Vendor SDK is now available
})
.catch(function (error) {
console.error(error.message);
});

var widgetDiv = document.createElement('div');
widgetDiv.id = 'vendor-widget';
var shadowRoot = widgetDiv.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = '<div></div>';
document.body.appendChild(widgetDiv);

The third-party SDK then mounts into this Shadow DOM container. Any styles injected by the vendor SDK are scoped to the shadow tree and do not leak into the surrounding document.


Window Size Negotiation

Mobile Aspects must communicate their layout requirements to the native app so it can size and position the WebView correctly. This concept does not exist on web — web Aspects rely on CSS for positioning.

The sizeAndLocation bridge accepts an aspectLocations array with bounding box coordinates relative to the screen:

function resizeWindow(aspectDetails) {
var jsonString = JSON.stringify(aspectDetails);
if (navigator.userAgent && navigator.userAgent.match(/(iPad|iPhone|iPod)/i)) {
window.webkit.messageHandlers.sizeAndLocation.postMessage(jsonString);
} else {
JSBridge.sizeAndLocation(jsonString);
}
}

aspectLocations payload

FieldTypeDescription
xstringLeft position of the Aspect area (pixels)
ystringTop position of the Aspect area (pixels)
widthstringWidth of the Aspect area (pixels)
heightstringHeight of the Aspect area (pixels)
webWidthstringTotal WebView width (window.screen.width)
webHeightstringTotal WebView height (window.screen.height)

Condensed State

When the widget is collapsed (e.g. showing only a floating button), report the button's bounding box so the native app can minimize the WebView to just that area:

function condenseWindow() {
var rect = chatButton.getBoundingClientRect();
var aspectDetails = {
aspectLocations: [{
x: '' + rect.x,
y: '' + rect.y,
width: '' + (rect.width * 1.25),
height: '' + (rect.height * 1.25),
webWidth: '' + window.screen.width,
webHeight: '' + window.screen.height
}]
};
resizeWindow(aspectDetails);
}

Expanded State

When the widget is open (e.g. a full chat window), report full-screen dimensions so the native app expands the WebView to cover the app:

function expandWindow() {
var aspectDetails = {
aspectLocations: [{
x: '0',
y: '0',
width: '' + window.screen.width,
height: '' + window.screen.height,
webWidth: '' + window.screen.width,
webHeight: '' + window.screen.height
}]
};
resizeWindow(aspectDetails);
}

Widget State Tracking with MutationObserver

Because third-party SDKs manage their own UI state (open, closed, minimized), the Aspect needs to detect state changes and update the native app's window size accordingly. Use a MutationObserver to watch for attribute changes on the vendor widget's DOM element:

function observeWidgetState(widgetElement) {
var observer = new MutationObserver(function (mutationsList) {
for (var i = 0; i < mutationsList.length; i++) {
var mutation = mutationsList[i];
if (mutation.type === 'attributes') {
var isVisible = mutation.target.getAttribute(mutation.attributeName) === 'true';
if (isVisible) {
expandWindow();
} else {
condenseWindow();
}
}
}
});

observer.observe(widgetElement, {
attributes: true,
attributeFilter: ['data-visible'],
attributeOldValue: true
});
}

The specific attribute to observe (data-visible in the example above) depends on your vendor's SDK. Check the vendor's documentation for the attribute or event that signals open/close state.


Waiting for Async Elements

Third-party SDKs mount their UI asynchronously. Use a MutationObserver-based utility to wait for elements to appear inside the Shadow DOM before interacting with them:

function waitForElement(root, selector, timeout) {
timeout = timeout || 10000;
return new Promise(function (resolve, reject) {
var element = root.querySelector(selector);
if (element) {
resolve(element);
return;
}

var observer = new MutationObserver(function (mutations) {
for (var i = 0; i < mutations.length; i++) {
var addedNodes = mutations[i].addedNodes;
for (var j = 0; j < addedNodes.length; j++) {
var node = addedNodes[j];
if (node.nodeType === Node.ELEMENT_NODE) {
if (node.matches && node.matches(selector)) {
observer.disconnect();
clearTimeout(timeoutId);
resolve(node);
return;
}
var found = root.querySelector(selector);
if (found) {
observer.disconnect();
clearTimeout(timeoutId);
resolve(found);
return;
}
}
}
}
});

observer.observe(root, { childList: true, subtree: true });

var timeoutId = setTimeout(function () {
observer.disconnect();
reject(new Error('Element not found: ' + selector));
}, timeout);
});
}

Usage:

var host = document.getElementById('vendor-widget');
waitForElement(host.shadowRoot, 'div.vendor-launcher')
.then(function (launcherElement) {
// Launcher is ready — set up state observation and initial layout
observeWidgetState(launcherElement);
condenseWindow();
})
.catch(function (error) {
console.error(error.message);
});

Security Considerations

The same security best practices apply as for web Aspects, with additional mobile-specific guidance:

  • Bridge payloads should never contain secrets. The tokenApiDetails bridge sends request metadata (URL, method, headers) — the native app adds credentials. Do not include access tokens or passwords in the payload.
  • Validate all data received from the native bridge. Treat window.dbk.tokenResponse as external input and validate its structure before use.
  • Optionally use Shadow DOM to isolate third-party widget styles and scripts from the WebView document.

Next Steps